home *** CD-ROM | disk | FTP | other *** search
- /*
- PLAY.C
-
- 29-jun-88, Marc Savary, Ad Lib Inc.
-
- Demo file to play the AdLib MIDI music file ( *.MUS) with the *.SND
- timbre bank file.
- This simple driver use the PC's timer-0 for timing. (see TIMER.ASM )
-
- See "convert.c" for *.MUS file structure.
-
- This file and the file BANK.C (timbre bank manager) MUST BE compiled
- WITHOUT stack overflow check. (-v option of Lattice C Compiler,
- -Gs for Microsoft)
-
- If compiling with Lattice, 'LATTICE' must be defined for this file
- and BANK.C.
-
- With Microsoft, use the following ('MICROSOFT' must be defined for
- this file and BANK.C):
- masm timer.asm
- cl -DMICROSOFT -AS -J -Zi -Zp1 -Ox -Gs -c play.c bank.c adlib.c
- link play.obj adlib.obj bank.obj timer.obj
-
- */
-
- #include <stdio.h>
- #include <fcntl.h>
-
- #include "convert.h"
- #include "bank.h"
-
- #ifdef MICROSOFT
- #define UNSIGNED_CHAR unsigned char
- #endif
- #ifdef LATTICE
- #define UNSIGNED_CHAR char
- #endif
-
- BankPtr bank; /* pointer to instrument bank */
- char * musPtr; /* pointer to first data byte of melody */
- struct MusHeader * headPtr; /* pointer to header of .MUS file */
- char musRunning; /* != 0 if music is playing */
- UNSIGNED_CHAR status; /* running status byte */
- char volume[ NR_VOICES]; /* actual volume of all voices */
-
- long tickCount; /* time counter, for information only */
- unsigned delay; /* length of last delay */
-
- #ifdef LATTICE
- extern char * getmem();
- extern int allmem();
- extern int rlsmem();
- #endif
-
- #ifdef MICROSOFT
- #include <malloc.h>
- #include <string.h>
- #define rlsmem(x,y) free(x)
- #define getmem(x) malloc(x)
- #define setmem(x,y,z) memset(x,z,y)
- #define movmem(x,y,z) memmove(y,x,z)
- #define max(x,y) ((x > y) ? x:y)
- #endif
-
-
- /*
- Simple demonstration.
-
- Syntax: play <musfile.mus> <bankfile.snd>
- */
- main( argc, argv)
- int argc;
- char * argv[];
- {
- struct MusHeader mH;
- int meloFile;
- char * music;
- unsigned len;
- BankPtr theBank;
-
- if( argc < 3) {
- fprintf( stderr, "\nUSE: play <musfile.mus> <bankfile.snd>");
- exit( 1);
- }
-
- /* Perform some initialisations ... */
- Init();
-
- if( NULL == ( theBank = OpenBank( argv[ 2], 0))) {
- fprintf( stderr, "\nUnable to open timbre bank file '%s'.", argv[ 2]);
- Terminate();
- exit( 1);
- }
- if( !LoadBank( theBank)) {
- fprintf( stderr, "\nError while reading timbre bank.");
- Terminate();
- exit( 1);
- }
- meloFile = open( argv[ 1], O_RDONLY + O_RAW);
- if( -1 == meloFile) {
- fprintf( stderr, "\nUnable to open music file '%s'.", argv[ 1]);
- Terminate();
- exit( 1);
- }
-
- /* read the music file's header: */
- read( meloFile, &mH, sizeof( struct MusHeader));
- len = mH.dataSize;
- music = (char *) getmem( (unsigned) len);
- if( music == NULL) {
- fprintf( stderr, "\nMemory allocation error.");
- exit( 1);
- }
-
- /* load all the data in memory: */
- read( meloFile, music, len);
-
- /* Start playing: */
- StartMelo( &mH, music, len, theBank);
-
- /* wait until end.... */
- WaitEndMelo();
-
- /* uninstall the clock driver: */
- Terminate();
-
- rlsmem( music, len);
- CloseBank( theBank);
- } /* main() */
-
-
- /*
- Wait until the end of melody ( musRunning == 0)
- */
- WaitEndMelo()
- {
- static unsigned measure = 0, beat = 0;
- unsigned m, b, c, i;
-
- /* cprintf( "\nPress ESC key to stop"); */
- while( musRunning) {
- if( kbhit()) {
- c = getch();
- if( c == 0x1b)
- StopMelo();
- }
- #ifdef DEBUG
- m = tickCount / (headPtr->beatMeasure * headPtr->tickBeat);
- b = tickCount / headPtr->tickBeat;
- if( m != measure) {
- printf( "\nMeasure: %03d ", m);
- measure = m;
- }
- if( b != beat) {
- printf( "+ ");
- beat = b;
- }
- #endif
- }
- } /* WaitEndMelo() */
-
-
- /*
- Initialize the driver.
- */
- Init()
- {
- /* initalize the low-level sound-driver: */
- if( !SoundColdInit( 0x388)) {
- printf( "\nAdlib board not found!");
- exit( 1);
- }
-
- /* allocate all the memory available:*/
- #ifdef LATTICE
- allmem();
- #endif
- /* install the clock driver: */
- Clk_install();
- }
-
-
- /*
- Uninstall the clock driver ...
- */
- Terminate()
- {
- Clk_uninstall();
- }
-
-
- /*
- Start playing a melody. Set some global pointers for the interrupt
- routine. Set the tempo, sound mode & pitch bend range.
- Reset volume of each voice. Start the clock driver with the first
- delay ( >= 1)
- */
- StartMelo( header, data, len, timBank)
- struct MusHeader * header; /* pointer to header struc. of music file */
- char * data; /* pointer to music data */
- unsigned len; /* size of data */
- BankPtr timBank; /* bank of timbres */
- {
- int i;
-
- musPtr = data;
- headPtr = header;
- bank = timBank;
- tickCount = 0;
- for( i = 0; i < NR_VOICES; i++)
- volume[ i] = 0;
-
- SetMode( header->soundMode);
- SetPitchRange( header->pitchBRange);
- SetTempo( header->basicTempo, header->tickBeat);
-
- StartTimeOut( 0);
- delay = *musPtr++;
- musRunning = 1;
- /* NEVER START COUNT-DOWN WITH 0, since 0 means MAXIMUM delay!!! */
- StartTimeOut( max( delay, 1));
- } /* StartMelo() */
-
-
- /*
- Stop playing the melody. Send note-off to all voices and reset
- the clock frequency to nominal ( 18.2 Hz).
- */
- StopMelo()
- {
- int i;
-
- musRunning = 0;
- for( i = 0; i < NR_VOICES; i++)
- NoteOff( i);
- SetTempo( 0, 1);
- } /* StopMelo() */
-
-
- /*
- Change the tempo.
-
- Reload the timer-0 with the proper divider for generating
- the appropriate frequency.
-
- If tempo is zero, reprogram the counter for 18.2 Hz.
- */
- SetTempo( tempo, tickBeat)
- unsigned tempo; /* beats per minute */
- unsigned tickBeat; /* ticks per beat */
- {
- long t1;
- unsigned freq;
- unsigned low, high, flags, count;
-
- t1 = tickBeat * (long)tempo;
- freq = t1 /60; /* interrupt rate needed */
-
- if( !freq)
- count = 0;
- else {
- /* make sure that frequency is >= 19 Hz, since counter min. output
- frequency is 18.2 Hz: */
- freq = freq < 19 ? 19 : freq;
- /* compute counter divider: */
- count = (1193180 /(long)freq);
- }
- /* and set the counter: */
- SetClkRate( count);
- } /* SetTempo() */
-
-
-
- /*
- Interrupt routine. Called by low-level clock driver when
- the delay count has expired.
-
- 'musPtr' always points to an OVERFLOW BYTE or to the first byte AFTER
- the timing byte.
-
- When this routine is called, the active SS ( stack segment) is not
- the original of the application, so take care.
- This routine, and all others called by, must be compiled
- without stack-overflow checking, since the SS has changed!!!
-
- Return to caller the number of clock ticks to wait for before
- the next call.
- */
- unsigned TimeOut()
- {
- unsigned pitch, tempo, haut, vol;
- UNSIGNED_CHAR newStatus;
- int timbreDef[ TIMBRE_DEF_LEN], timbre;
- int comm, id, integer, frac, voice = 1;
-
- if( ! musRunning)
- /* Music has not started or has been stopped, so wait the minimum delay ... */
- return 1;
-
- tickCount += delay;
- do {
- newStatus = *musPtr;
- if( newStatus == OVERFLOW_BYTE) {
- /* timing overflow ... */
- musPtr++;
- delay = OVERFLOW_BYTE;
- break;
- }
-
- else if( newStatus == STOP_BYTE) {
- StopMelo();
- return 0; /* maximum delay ... */
- }
- else if( newStatus == SYSTEM_XOR_BYTE) {
- /*
- non-standard... this is a tempo multiplier:
- data format: <F0> <7F> <00> <integer> <frac> <F7>
- tempo = basicTempo * integerPart + basicTempo * fractionPart/128
- */
- musPtr++;
- id = *musPtr++;
- comm = *musPtr++;
- if( id != ADLIB_CTRL_BYTE || comm != TEMPO_CTRL_BYTE) {
- /* unknown format ... skip all the XOR message */
- musPtr -= 2;
- while( *musPtr++ != EOX_BYTE)
- ;
- }
- else {
- integer = *musPtr++;
- frac = *musPtr++;
- tempo = headPtr->basicTempo;
- tempo = tempo * integer + (unsigned)(((long)tempo * frac) >> 7);
- SetTempo( tempo, headPtr->tickBeat);
- musPtr++; /* skip EOX_BYTE */
- }
- delay = *musPtr++;
- }
- else {
- if( newStatus >= 0x80) {
- musPtr++;
- status = newStatus;
- }
- voice = (int) (status & 0x0f);
-
- switch( status & 0xf0) {
- case NOTE_ON_BYTE:
- haut = *musPtr++;
- vol = *musPtr++;
- if( ! vol) {
- NoteOff( voice);
- }
- else {
- if( vol != volume[ voice]) {
- SetVoiceVolume( voice, vol);
- volume[ voice] = vol;
- }
- NoteOn( voice, haut);
- }
- break;
-
- case NOTE_OFF_BYTE:
- musPtr += 2;
- NoteOff( voice);
- break;
-
- case AFTER_TOUCH_BYTE:
- SetVoiceVolume( voice, *musPtr++);
- break;
-
- case PROG_CHANGE_BYTE:
- timbre = *musPtr++;
- if( GetTimbre( "", &timbre, timbreDef, bank)) {
- SetVoiceTimbre( voice, timbreDef);
- }
- else
- cprintf( "\nTimbre not found: %d", timbre);
- break;
-
- case PITCH_BEND_BYTE:
- pitch = *musPtr++;
- pitch += (unsigned)(*musPtr++) << 7;
- SetVoicePitch( voice, pitch);
- break;
-
- case CONTROL_CHANGE_BYTE:
- /* not implemented ... */
- musPtr += 2;
- break;
-
- case CHANNEL_PRESSURE_BYTE:
- /* not implemented ... */
- musPtr++;
- break;
-
- default:
- cprintf( "\nBad MIDI status byte: %d", status);
- SkipToTiming();
- break;
- }
-
- delay = *musPtr++;
- }
- } while( delay == 0);
-
- if( delay == OVERFLOW_BYTE) {
- delay = OVERFLOW;
- if( *musPtr != OVERFLOW_BYTE)
- delay += *musPtr++;
- }
- return delay;
- }
-
-
- /*
- A bad status byte ( or unimplemented MIDI command) has been encontered.
- Skip bytes until next timing byte followed by status byte.
- */
- static SkipToTiming()
- {
- while( *musPtr < 0x80)
- musPtr++;
- if( *musPtr != OVERFLOW_BYTE)
- musPtr--;
- } /* SkipToTiming() */
-
-